/* s_gen.c
 *
 * Micropolis, Unix Version.  This game was released for the Unix platform
 * in or about 1990 and has been modified for inclusion in the One Laptop
 * Per Child program.  Copyright (C) 1989 - 2007 Electronic Arts Inc.  If
 * you need assistance with this program, you may contact:
 *   http://wiki.laptop.org/go/Micropolis  or email  micropolis@laptop.org.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.  You should have received a
 * copy of the GNU General Public License along with this program.  If
 * not, see <http://www.gnu.org/licenses/>.
 * 
 *             ADDITIONAL TERMS per GNU GPL Section 7
 * 
 * No trademark or publicity rights are granted.  This license does NOT
 * give you any right, title or interest in the trademark SimCity or any
 * other Electronic Arts trademark.  You may not distribute any
 * modification of this program using the trademark SimCity or claim any
 * affliation or association with Electronic Arts Inc. or its employees.
 * 
 * Any propagation or conveyance of this program must include this
 * copyright notice and these terms.
 * 
 * If you convey this program (or any modifications of it) and assume
 * contractual liability for the program to recipients of it, you agree
 * to indemnify Electronic Arts for any liability that those contractual
 * assumptions impose on Electronic Arts.
 * 
 * You may not misrepresent the origins of this program; modified
 * versions of the program must be marked as such and not identified as
 * the original program.
 * 
 * This disclaimer supplements the one included in the General Public
 * License.  TO THE FULLEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW, THIS
 * PROGRAM IS PROVIDED TO YOU "AS IS," WITH ALL FAULTS, WITHOUT WARRANTY
 * OF ANY KIND, AND YOUR USE IS AT YOUR SOLE RISK.  THE ENTIRE RISK OF
 * SATISFACTORY QUALITY AND PERFORMANCE RESIDES WITH YOU.  ELECTRONIC ARTS
 * DISCLAIMS ANY AND ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES,
 * INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY, SATISFACTORY QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT OF THIRD PARTY
 * RIGHTS, AND WARRANTIES (IF ANY) ARISING FROM A COURSE OF DEALING,
 * USAGE, OR TRADE PRACTICE.  ELECTRONIC ARTS DOES NOT WARRANT AGAINST
 * INTERFERENCE WITH YOUR ENJOYMENT OF THE PROGRAM; THAT THE PROGRAM WILL
 * MEET YOUR REQUIREMENTS; THAT OPERATION OF THE PROGRAM WILL BE
 * UNINTERRUPTED OR ERROR-FREE, OR THAT THE PROGRAM WILL BE COMPATIBLE
 * WITH THIRD PARTY SOFTWARE OR THAT ANY ERRORS IN THE PROGRAM WILL BE
 * CORRECTED.  NO ORAL OR WRITTEN ADVICE PROVIDED BY ELECTRONIC ARTS OR
 * ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY.  SOME
 * JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON IMPLIED
 * WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A
 * CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY
 * NOT APPLY TO YOU.
 */
#include "sim.h"

/* Generate Map */

#define WATER_LOW	RIVER       /* 2 */
#define WATER_HIGH	LASTRIVEDGE /* 20 */
#define WOODS_LOW	TREEBASE    /* 21 */
#define WOODS_HIGH	UNUSED_TRASH2   /* 39 */

short XStart, YStart, MapX, MapY;
short Dir, LastDir;
int TreeLevel = -1;             /* level for tree creation */
int LakeLevel = -1;             /* level for lake creation */
int CurveLevel = -1;            /* level for river curviness */
int CreateIsland = -1;          /* -1 => 10%, 0 => never, 1 => always */

void DoBRiv(void);
void DoSRiv(void);
void BRivPlop(void);
void SRivPlop(void);
void DoRivers(void);
void DoTrees(void);
void GetRandStart(void);
void MakeLakes(void);
void MakeIsland(void);
void MakeNakedIsland(void);
void GenerateMap(int r);

void GenerateNewCity(void)
{
    GenerateSomeCity(Rand16());
}

void GenerateSomeCity(int r)
{
    if(CityFileName != NULL)
    {
        ckfree(CityFileName);
        CityFileName = NULL;
    }

    gettimeofday(&start_time, NULL);

    GenerateMap(r);
    ScenarioID = 0;
    CityTime = 0;
    InitSimLoad = 2;
    DoInitialEval = 0;

    InitWillStuff();
    ResetMapState();
    ResetEditorState();
    InvalidateEditors();
    InvalidateMaps();
    UpdateFunds();
    DoSimInit();
    Eval("UIDidGenerateNewCity");
    Kick();
}

int ERand(short limit)
{
    short x, z;

    z = Rand(limit);
    x = Rand(limit);
    if(z < x)
        return (z);
    return (x);
}

void GenerateMap(int r)
{
    SeedRand(r);

    if(CreateIsland < 0)
    {
        if(Rand(100) < 10)
        {                       /* chance that island is generated */
            MakeIsland();
            return;
        }
    }
    if(CreateIsland == 1)
    {
        MakeNakedIsland();
    }
    else
    {
        ClearMap();
    }
    GetRandStart();
    if(CurveLevel != 0)
    {
        DoRivers();
    }
    if(LakeLevel != 0)
    {
        MakeLakes();
    }
    SmoothRiver();
    if(TreeLevel != 0)
    {
        DoTrees();
    }
    RandomlySeedRand();
}

void ClearMap(void)
{
    register short x, y;

    for(x = 0; x < WORLD_X; x++)
        for(y = 0; y < WORLD_Y; y++)
            Map[x][y] = DIRT;
}

void ClearUnnatural(void)
{
    register short x, y;

    for(x = 0; x < WORLD_X; x++)
    {
        for(y = 0; y < WORLD_Y; y++)
        {
            if(Map[x][y] > WOODS)
            {
                Map[x][y] = DIRT;
            }
        }
    }
}

#define RADIUS 18

void MakeNakedIsland(void)
{
    register int x, y;

    for(x = 0; x < WORLD_X; x++)
        for(y = 0; y < WORLD_Y; y++)
            Map[x][y] = RIVER;
    for(x = 5; x < WORLD_X - 5; x++)
        for(y = 5; y < WORLD_Y - 5; y++)
            Map[x][y] = DIRT;
    for(x = 0; x < WORLD_X - 5; x += 2)
    {
        MapX = x;
        MapY = ERand(RADIUS);
        BRivPlop();
        MapY = (WORLD_Y - 10) - ERand(RADIUS);
        BRivPlop();
        MapY = 0;
        SRivPlop();
        MapY = (WORLD_Y - 6);
        SRivPlop();
    }
    for(y = 0; y < WORLD_Y - 5; y += 2)
    {
        MapY = y;
        MapX = ERand(RADIUS);
        BRivPlop();
        MapX = (WORLD_X - 10) - ERand(RADIUS);
        BRivPlop();
        MapX = 0;
        SRivPlop();
        MapX = (WORLD_X - 6);
        SRivPlop();
    }
}

void MakeIsland(void)
{
    MakeNakedIsland();
    SmoothRiver();
    DoTrees();
}

void MakeLakes(void)
{
    short Lim1, Lim2, t, z;
    register short x, y;

    if(LakeLevel < 0)
    {
        Lim1 = Rand(10);
    }
    else
    {
        Lim1 = LakeLevel / 2;
    }
    for(t = 0; t < Lim1; t++)
    {
        x = Rand(WORLD_X - 21) + 10;
        y = Rand(WORLD_Y - 20) + 10;
        Lim2 = Rand(12) + 2;
        for(z = 0; z < Lim2; z++)
        {
            MapX = x - 6 + Rand(12);
            MapY = y - 6 + Rand(12);
            if(Rand(4))
                SRivPlop();
            else
                BRivPlop();
        }
    }
}

void GetRandStart(void)
{
    XStart = 40 + Rand(WORLD_X - 80);
    YStart = 33 + Rand(WORLD_Y - 67);
    MapX = XStart;
    MapY = YStart;
}

void MoveMap(short dir)
{
    static short DirTab[2][8] = { {0, 1, 1, 1, 0, -1, -1, -1},
    {-1, -1, 0, 1, 1, 1, 0, -1}
    };
    dir = dir & 7;
    MapX += DirTab[0][dir];
    MapY += DirTab[1][dir];
}

void TreeSplash(short xloc, short yloc)
{
    short dis, dir;
    register short z;

    if(TreeLevel < 0)
    {
        dis = Rand(150) + 50;
    }
    else
    {
        dis = Rand(100 + (TreeLevel * 2)) + 50;
    }
    MapX = xloc;
    MapY = yloc;
    for(z = 0; z < dis; z++)
    {
        dir = Rand(7);
        MoveMap(dir);
        if(!(TestBounds(MapX, MapY)))
            return;
        if((Map[MapX][MapY] & LOMASK) == DIRT)
            Map[MapX][MapY] = WOODS + BLBNBIT;
    }
}

void DoTrees(void)
{
    short Amount, x, xloc, yloc;

    if(TreeLevel < 0)
    {
        Amount = Rand(100) + 50;
    }
    else
    {
        Amount = TreeLevel + 3;
    }
    for(x = 0; x < Amount; x++)
    {
        xloc = Rand(WORLD_X - 1);
        yloc = Rand(WORLD_Y - 1);
        TreeSplash(xloc, yloc);
    }
    SmoothTrees();
    SmoothTrees();
}

void SmoothRiver(void)
{
    static short DX[4] = { -1, 0, 1, 0 };
    static short DY[4] = { 0, 1, 0, -1 };
    static short REdTab[16] = {
        13 + BULLBIT, 13 + BULLBIT, 17 + BULLBIT, 15 + BULLBIT,
        5 + BULLBIT, 2, 19 + BULLBIT, 17 + BULLBIT,
        9 + BULLBIT, 11 + BULLBIT, 2, 13 + BULLBIT,
        7 + BULLBIT, 9 + BULLBIT, 5 + BULLBIT, 2
    };
    short bitindex, z, Xtem, Ytem;
    register short temp, MapX, MapY;

    for(MapX = 0; MapX < WORLD_X; MapX++)
    {
        for(MapY = 0; MapY < WORLD_Y; MapY++)
        {
            if(Map[MapX][MapY] == REDGE)
            {
                bitindex = 0;
                for(z = 0; z < 4; z++)
                {
                    bitindex = bitindex << 1;
                    Xtem = MapX + DX[z];
                    Ytem = MapY + DY[z];
                    if(TestBounds(Xtem, Ytem) &&
                       ((Map[Xtem][Ytem] & LOMASK) != DIRT) &&
                       (((Map[Xtem][Ytem] & LOMASK) < WOODS_LOW) || ((Map[Xtem][Ytem] & LOMASK) > WOODS_HIGH)))
                        bitindex++;
                }
                temp = REdTab[bitindex & 15];
                if((temp != RIVER) && (Rand(1)))
                    temp++;
                Map[MapX][MapY] = temp;
            }
        }
    }
}

int IsTree(int cell)
{
    if(((cell & LOMASK) >= WOODS_LOW) && ((cell & LOMASK) <= WOODS_HIGH))
        return TRUE;
    return FALSE;
}

void SmoothTrees(void)
{
    register short MapX, MapY;

    for(MapX = 0; MapX < WORLD_X; MapX++)
    {
        for(MapY = 0; MapY < WORLD_Y; MapY++)
        {
            SmoothTreesAt(MapX, MapY);
        }
    }
}

void SmoothTreesAt(short MapX, short MapY)
{
    int i;
    for (i=0 ; i<3 ; i++) {
        SmoothTreesCoord(MapX, MapY);
        
        SmoothTreesCoord(MapX-1, MapY);
        SmoothTreesCoord(MapX+1, MapY);
        SmoothTreesCoord(MapX, MapY-1);
        SmoothTreesCoord(MapX, MapY+1);

        SmoothTreesCoord(MapX-1, MapY-1);
        SmoothTreesCoord(MapX-1, MapY+1);
        SmoothTreesCoord(MapX+1, MapY-1);
        SmoothTreesCoord(MapX+1, MapY+1);
    }
}

void SmoothTreesCoord(short atx, short aty)
{
    static short DX[4] = { -1, 0, 1, 0 };
    static short DY[4] = { 0, 1, 0, -1 };
    static short TEdTab[16] = { 0, 0, 0, 34,
        0, 0, 36, 35,
        0, 32, 0, 33,
        30, 31, 29, 37
    };
    short bitindex, z, Xtem, Ytem;
    register short temp;

    if (atx >= WORLD_X || atx < 0) return;
    if (aty >= WORLD_Y || aty < 0) return;

    if(IsTree(Map[atx][aty]))
    {
        bitindex = 0;
        for(z = 0; z < 4; z++)
        {
            bitindex = bitindex << 1;
            Xtem = atx + DX[z];
            Ytem = aty + DY[z];
            if(TestBounds(Xtem, Ytem) && IsTree(Map[Xtem][Ytem]))
            {
                bitindex++;
            }
        }
        temp = TEdTab[bitindex & 15];
        if(temp)
        {
            if(temp != WOODS)
                if((atx + aty) & 1)
                    temp = temp - 8;
            Map[atx][aty] = temp + BLBNBIT;
        }
        else
            Map[atx][aty] = temp;
    }
}

void DoRivers(void)
{

    LastDir = Rand(3);
    Dir = LastDir;
    DoBRiv();
    MapX = XStart;
    MapY = YStart;
    LastDir = LastDir ^ 4;
    Dir = LastDir;
    DoBRiv();
    MapX = XStart;
    MapY = YStart;
    LastDir = Rand(3);
    DoSRiv();
}

void DoBRiv(void)
{
    int r1, r2;

    if(CurveLevel < 0)
    {
        r1 = 100;
        r2 = 200;
    }
    else
    {
        r1 = CurveLevel + 10;
        r2 = CurveLevel + 100;
    }

    while(TestBounds(MapX + 4, MapY + 4))
    {
        BRivPlop();
        if(Rand(r1) < 10)
        {
            Dir = LastDir;
        }
        else
        {
            if(Rand(r2) > 90)
                Dir++;
            if(Rand(r2) > 90)
                Dir--;
        }
        MoveMap(Dir);
    }
}

void DoSRiv(void)
{
    int r1, r2;

    if(CurveLevel < 0)
    {
        r1 = 100;
        r2 = 200;
    }
    else
    {
        r1 = CurveLevel + 10;
        r2 = CurveLevel + 100;
    }

    while(TestBounds(MapX + 3, MapY + 3))
    {
        SRivPlop();
        if(Rand(r1) < 10)
        {
            Dir = LastDir;
        }
        else
        {
            if(Rand(r2) > 90)
                Dir++;
            if(Rand(r2) > 90)
                Dir--;
        }
        MoveMap(Dir);
    }
}

void PutOnMap(short Mchar, short Xoff, short Yoff)
{
    register short Xloc, Yloc, temp;

    if(Mchar == 0)
        return;
    Xloc = MapX + Xoff;
    Yloc = MapY + Yoff;
    if(TestBounds(Xloc, Yloc) == FALSE)
        return;
    if((temp = Map[Xloc][Yloc]))
    {
        temp = temp & LOMASK;
        if(temp == RIVER)
            if(Mchar != CHANNEL)
                return;
        if(temp == CHANNEL)
            return;
    }
    Map[Xloc][Yloc] = Mchar;
}

void BRivPlop(void)
{
    static short BRMatrix[9][9] = {
        {0, 0, 0, 3, 3, 3, 0, 0, 0},
        {0, 0, 3, 2, 2, 2, 3, 0, 0},
        {0, 3, 2, 2, 2, 2, 2, 3, 0},
        {3, 2, 2, 2, 2, 2, 2, 2, 3},
        {3, 2, 2, 2, 4, 2, 2, 2, 3},
        {3, 2, 2, 2, 2, 2, 2, 2, 3},
        {0, 3, 2, 2, 2, 2, 2, 3, 0},
        {0, 0, 3, 2, 2, 2, 3, 0, 0},
        {0, 0, 0, 3, 3, 3, 0, 0, 0}
    };
    short x, y;

    for(x = 0; x < 9; x++)
        for(y = 0; y < 9; y++)
            PutOnMap(BRMatrix[y][x], x, y);
}

void SRivPlop(void)
{
    static short SRMatrix[6][6] = {
        {0, 0, 3, 3, 0, 0},
        {0, 3, 2, 2, 3, 0},
        {3, 2, 2, 2, 2, 3},
        {3, 2, 2, 2, 2, 3},
        {0, 3, 2, 2, 3, 0},
        {0, 0, 3, 3, 0, 0}
    };
    short x, y;

    for(x = 0; x < 6; x++)
        for(y = 0; y < 6; y++)
            PutOnMap(SRMatrix[y][x], x, y);
}

void SmoothWater(void)
{
    int x, y;

    for(x = 0; x < WORLD_X; x++)
    {
        for(y = 0; y < WORLD_Y; y++)
        {
            /*
             * If water: 
             */
            if(((Map[x][y] & LOMASK) >= WATER_LOW) && ((Map[x][y] & LOMASK) <= WATER_HIGH))
            {
                if(x > 0)
                {
                    /*
                     * If nearest object is not water: 
                     */
                    if(((Map[x - 1][y] & LOMASK) < WATER_LOW) || ((Map[x - 1][y] & LOMASK) > WATER_HIGH))
                    {
                        goto edge;
                    }
                }
                if(x < (WORLD_X - 1))
                {
                    /*
                     * If nearest object is not water: 
                     */
                    if(((Map[x + 1][y] & LOMASK) < WATER_LOW) || ((Map[x + 1][y] & LOMASK) > WATER_HIGH))
                    {
                        goto edge;
                    }
                }
                if(y > 0)
                {
                    /*
                     * If nearest object is not water: 
                     */
                    if(((Map[x][y - 1] & LOMASK) < WATER_LOW) || ((Map[x][y - 1] & LOMASK) > WATER_HIGH))
                    {
                        goto edge;
                    }
                }
                if(y < (WORLD_Y - 1))
                {
                    /*
                     * If nearest object is not water: 
                     */
                    if(((Map[x][y + 1] & LOMASK) < WATER_LOW) || ((Map[x][y + 1] & LOMASK) > WATER_HIGH))
                    {
                      edge:
                        Map[x][y] = REDGE;  /* set river edge */
                        continue;
                    }
                }
            }
        }
    }
    for(x = 0; x < WORLD_X; x++)
    {
        for(y = 0; y < WORLD_Y; y++)
        {
            /*
             * If water which is not a channel: 
             */
            if(((Map[x][y] & LOMASK) != CHANNEL) &&
               ((Map[x][y] & LOMASK) >= WATER_LOW) && ((Map[x][y] & LOMASK) <= WATER_HIGH))
            {
                if(x > 0)
                {
                    /*
                     * If nearest object is not water; 
                     */
                    if(((Map[x - 1][y] & LOMASK) < WATER_LOW) || ((Map[x - 1][y] & LOMASK) > WATER_HIGH))
                    {
                        continue;
                    }
                }
                if(x < (WORLD_X - 1))
                {
                    /*
                     * If nearest object is not water: 
                     */
                    if(((Map[x + 1][y] & LOMASK) < WATER_LOW) || ((Map[x + 1][y] & LOMASK) > WATER_HIGH))
                    {
                        continue;
                    }
                }
                if(y > 0)
                {
                    /*
                     * If nearest object is not water: 
                     */
                    if(((Map[x][y - 1] & LOMASK) < WATER_LOW) || ((Map[x][y - 1] & LOMASK) > WATER_HIGH))
                    {
                        continue;
                    }
                }
                if(y < (WORLD_Y - 1))
                {
                    /*
                     * If nearest object is not water: 
                     */
                    if(((Map[x][y + 1] & LOMASK) < WATER_LOW) || ((Map[x][y + 1] & LOMASK) > WATER_HIGH))
                    {
                        continue;
                    }
                }
                Map[x][y] = RIVER;  /* make it a river */
            }
        }
    }
    for(x = 0; x < WORLD_X; x++)
    {
        for(y = 0; y < WORLD_Y; y++)
        {
            /*
             * If woods: 
             */
            if(((Map[x][y] & LOMASK) >= WOODS_LOW) && ((Map[x][y] & LOMASK) <= WOODS_HIGH))
            {
                if(x > 0)
                {
                    /*
                     * If nearest object is water: 
                     */
                    if((Map[x - 1][y] == RIVER) || (Map[x - 1][y] == CHANNEL))
                    {
                        Map[x][y] = REDGE;  /* make it water's edge */
                        continue;
                    }
                }
                if(x < (WORLD_X - 1))
                {
                    /*
                     * If nearest object is water: 
                     */
                    if((Map[x + 1][y] == RIVER) || (Map[x + 1][y] == CHANNEL))
                    {
                        Map[x][y] = REDGE;  /* make it water's edge */
                        continue;
                    }
                }
                if(y > 0)
                {
                    /*
                     * If nearest object is water: 
                     */
                    if((Map[x][y - 1] == RIVER) || (Map[x][y - 1] == CHANNEL))
                    {
                        Map[x][y] = REDGE;  /* make it water's edge */
                        continue;
                    }
                }
                if(y < (WORLD_Y - 1))
                {
                    /*
                     * If nearest object is water; 
                     */
                    if((Map[x][y + 1] == RIVER) || (Map[x][y + 1] == CHANNEL))
                    {
                        Map[x][y] = REDGE;  /* make it water's edge */
                        continue;
                    }
                }
            }
        }
    }
}
